summaryrefslogtreecommitdiff
path: root/app/[lng]/evcp/(evcp)/polices/page.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]/evcp/(evcp)/polices/page.tsx')
-rw-r--r--app/[lng]/evcp/(evcp)/polices/page.tsx238
1 files changed, 238 insertions, 0 deletions
diff --git a/app/[lng]/evcp/(evcp)/polices/page.tsx b/app/[lng]/evcp/(evcp)/polices/page.tsx
new file mode 100644
index 00000000..46a9e87a
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/polices/page.tsx
@@ -0,0 +1,238 @@
+// app/admin/policies/page.tsx (서버 컴포넌트)
+import { Suspense } from 'react'
+import { Metadata } from 'next'
+import { eq, desc } from 'drizzle-orm'
+import db from '@/db/db'
+import { policyVersions } from '@/db/schema'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+import { Separator } from '@/components/ui/separator'
+import { FileText, Shield, Calendar, User, Clock } from 'lucide-react'
+import { PolicyManagementClient } from '@/components/polices/policy-management-client'
+
+export const metadata: Metadata = {
+ title: '정책 관리 | eVCP Admin',
+ description: '개인정보 처리방침 및 이용약관 관리'
+}
+
+// 정책 데이터 조회 함수
+async function getPoliciesData() {
+ try {
+ // 현재 활성 정책들
+ const currentPolicies = await db
+ .select()
+ .from(policyVersions)
+ .where(eq(policyVersions.isCurrent, true))
+ .orderBy(policyVersions.policyType)
+
+ // 전체 정책 히스토리
+ const allPolicies = await db
+ .select()
+ .from(policyVersions)
+ .orderBy(desc(policyVersions.createdAt))
+
+ // 정책 타입별로 그룹화
+ const policiesByType = {
+ privacy_policy: allPolicies.filter(p => p.policyType === 'privacy_policy'),
+ terms_of_service: allPolicies.filter(p => p.policyType === 'terms_of_service')
+ }
+
+ // 현재 정책 맵
+ const currentPolicyMap = {}
+ currentPolicies.forEach(policy => {
+ currentPolicyMap[policy.policyType] = policy
+ })
+
+ return {
+ currentPolicies: currentPolicyMap,
+ allPolicies: policiesByType,
+ stats: {
+ totalVersions: allPolicies.length,
+ privacyVersions: policiesByType.privacy_policy.length,
+ termsVersions: policiesByType.terms_of_service.length,
+ lastUpdate: allPolicies[0]?.createdAt || null
+ }
+ }
+ } catch (error) {
+ console.error('Failed to fetch policies:', error)
+ return {
+ currentPolicies: {},
+ allPolicies: { privacy_policy: [], terms_of_service: [] },
+ stats: { totalVersions: 0, privacyVersions: 0, termsVersions: 0, lastUpdate: null }
+ }
+ }
+}
+
+export default async function PoliciesPage() {
+ const data = await getPoliciesData()
+
+ return (
+ <div className="container mx-auto py-6 space-y-6">
+ {/* 헤더 */}
+ <div className="flex items-center justify-between">
+ <div>
+ <h1 className="text-3xl font-bold tracking-tight">정책 관리</h1>
+ <p className="text-muted-foreground">
+ 개인정보 처리방침과 이용약관을 버전별로 관리합니다
+ </p>
+ </div>
+ </div>
+
+ {/* 통계 카드들 */}
+ <div className="grid gap-4 md:grid-cols-4">
+ <Card>
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium">총 버전 수</CardTitle>
+ <FileText className="h-4 w-4 text-muted-foreground" />
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">{data.stats.totalVersions}</div>
+ <p className="text-xs text-muted-foreground">
+ 전체 정책 버전
+ </p>
+ </CardContent>
+ </Card>
+
+ <Card>
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium">개인정보 정책</CardTitle>
+ <Shield className="h-4 w-4 text-muted-foreground" />
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">{data.stats.privacyVersions}</div>
+ <p className="text-xs text-muted-foreground">
+ 버전 수
+ </p>
+ </CardContent>
+ </Card>
+
+ <Card>
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium">이용약관</CardTitle>
+ <FileText className="h-4 w-4 text-muted-foreground" />
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">{data.stats.termsVersions}</div>
+ <p className="text-xs text-muted-foreground">
+ 버전 수
+ </p>
+ </CardContent>
+ </Card>
+
+ <Card>
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium">최근 업데이트</CardTitle>
+ <Clock className="h-4 w-4 text-muted-foreground" />
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">
+ {data.stats.lastUpdate
+ ? new Date(data.stats.lastUpdate).toLocaleDateString('ko-KR')
+ : 'N/A'
+ }
+ </div>
+ <p className="text-xs text-muted-foreground">
+ 마지막 정책 변경
+ </p>
+ </CardContent>
+ </Card>
+ </div>
+
+ {/* 현재 활성 정책들 */}
+ <div className="grid gap-6 md:grid-cols-2">
+ <CurrentPolicyCard
+ title="개인정보 처리방침"
+ icon={<Shield className="h-5 w-5" />}
+ policy={data.currentPolicies.privacy_policy}
+ type="privacy_policy"
+ />
+ <CurrentPolicyCard
+ title="이용약관"
+ icon={<FileText className="h-5 w-5" />}
+ policy={data.currentPolicies.terms_of_service}
+ type="terms_of_service"
+ />
+ </div>
+
+ <Separator />
+
+ {/* 클라이언트 컴포넌트로 편집 기능 제공 */}
+ <Suspense fallback={<PolicyManagementSkeleton />}>
+ <PolicyManagementClient initialData={data} />
+ </Suspense>
+ </div>
+ )
+}
+
+// 현재 정책 카드 컴포넌트
+function CurrentPolicyCard({ title, icon, policy, type }) {
+ if (!policy) {
+ return (
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ {icon}
+ {title}
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-center py-8 text-muted-foreground">
+ <p>아직 등록된 정책이 없습니다</p>
+ <p className="text-sm mt-2">새 버전을 생성해주세요</p>
+ </div>
+ </CardContent>
+ </Card>
+ )
+ }
+
+ return (
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ {icon}
+ {title}
+ <Badge variant="secondary">v{policy.version}</Badge>
+ </CardTitle>
+ <CardDescription>
+ 현재 활성 정책 • 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')}
+ </CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-3">
+ {/* 정책 내용 미리보기 */}
+ <div className="bg-muted/50 p-3 rounded-md text-sm max-h-32 overflow-hidden">
+ <div className="line-clamp-4">
+ {policy.content?.replace(/#{1,6}\s+/g, '').replace(/\*\*(.*?)\*\*/g, '$1').substring(0, 200)}...
+ </div>
+ </div>
+
+ {/* 메타 정보 */}
+ <div className="flex items-center justify-between text-xs text-muted-foreground">
+ <div className="flex items-center gap-1">
+ <Calendar className="h-3 w-3" />
+ 생성: {new Date(policy.createdAt).toLocaleDateString('ko-KR')}
+ </div>
+ <div className="flex items-center gap-1">
+ <User className="h-3 w-3" />
+ 관리자
+ </div>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ )
+}
+
+// 로딩 스켈레톤
+function PolicyManagementSkeleton() {
+ return (
+ <div className="space-y-4">
+ <div className="h-8 bg-muted animate-pulse rounded" />
+ <div className="grid gap-4 md:grid-cols-2">
+ <div className="h-32 bg-muted animate-pulse rounded" />
+ <div className="h-32 bg-muted animate-pulse rounded" />
+ </div>
+ <div className="h-96 bg-muted animate-pulse rounded" />
+ </div>
+ )
+} \ No newline at end of file